/******************************************************************************
 * SAGE - Scalable Adaptive Graphics Environment
 *
 * Module: pixelDownloader.cpp - manaing each application instance of SAGE Receiver
 * Author : Byungil Jeong
 *
 * Copyright (C) 2004 Electronic Visualization Laboratory,
 * University of Illinois at Chicago
 *
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the distribution.
 *  * Neither the name of the University of Illinois at Chicago nor
 *    the names of its contributors may be used to endorse or promote
 *    products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Direct questions, comments etc about SAGE to bijeong@evl.uic.edu or 
 * http://www.evl.uic.edu/cavern/forum/
 *
 *****************************************************************************/
 
#include "pixelDownloader.h"
#include "sageDisplay.h"
#include "sageSync.h"
#include "sageBlockPool.h"
#include "sageSharedData.h"
#include "sageBlock.h"
#include "sageEvent.h"
#include "sageBlockPartition.h"
#include "sageReceiver.h"

int montagePair::init(displayContext *context, sagePixFmt pfmt, int index, float depth)
{
   montage[0] = new sageMontage(context, pfmt);
   montage[1] = new sageMontage(context, pfmt);
   //tileID = id;
   setLocalTileIdx(index);
   setDepth(depth);
            
   return 0;
}

void montagePair::swapMontage() 
{ 
   // when a window is moved or resized, the config of back montage is
   // updated immediately, the front montage is updated when it is swapped
   
   if (renewMontage) {
      // copying the config of back montage to front montage
      montage[frontMon]->copyConfig(*montage[1-frontMon]);         
      renewMontage = false;
   }
         
   frontMon = 1 - frontMon; 
}

int montagePair::deleteMontage()
{
   montage[0]->deleteTexture();
   montage[1]->deleteTexture();
   
   return 0;
}

int montagePair::setDepth(float depth)
{
   montage[0]->depth = depth;
   montage[1]->depth = depth;
   
   return 0;
}

int montagePair::setLocalTileIdx(int idx)
{
   montage[0]->tileIdx = idx;
   montage[1]->tileIdx = idx;
   
   return 0;
}

montagePair::~montagePair()
{
   delete montage[0];
   delete montage[1];
}

pixelDownloader::pixelDownloader() : reportRate(1), updatedFrame(0), curFrame(0), recv(NULL),
   streamNum(0), bandWidth(0), montageList(NULL), configID(0), frameCheck(false),
   syncFrame(0), updateType(SAGE_UPDATE_FOLLOW), activeRcvs(0), passiveUpdate(false),
   dispConfigID(0), displayActive(false), status(PDL_WAIT_DATA), frameBlockNum(0), frameSize(0)
{
   perfTimer.reset();
}

int pixelDownloader::init(char *msg, dispSharedData *sh, streamProtocol *nwObj, bool sync)
{
   char *msgPt = sage::tokenSeek(msg, 3);
   sscanf(msgPt, "%d %d %d", &instID, &groupSize, &blockSize);
   
   int blockX, blockY, imgWidth, imgHeight;
   sagePixFmt pixFmt;
   
   msgPt = sage::tokenSeek(msg, 7);
   sscanf(msgPt, "%d %d %d %d %d", (int *)&pixFmt, &blockX, &blockY, &imgWidth, &imgHeight);
   partition = new sageBlockPartition(blockX, blockY, imgWidth, imgHeight);
   if (!partition) {
      sage::printLog("pixelDownloader::init : unable to create block partition");
      return -1;
   }   
   partition->initBlockTable();
   
   shared = sh;
   syncOn = sync;
         
   tileNum = shared->displayObj->getTileNum();
   montageList = new montagePair[tileNum];
   float depth = 1.0f - 0.01f*instID;

   for (int i=0; i<tileNum; i++) {
      montageList[i].init(shared->context, pixFmt, i, depth);
   }
   
   configQueue.clear();

   blockBuf = new sageBlockBuf(shared->bufSize, groupSize, blockSize, BUF_MEM_ALLOC | BUF_CTRL_GROUP);
   recv = new sagePixelReceiver(msg, (rcvSharedData *)shared, nwObj, blockBuf);
   
   return 0;
}

int pixelDownloader::addStream(int senderID)
{
   if (recv) {
      recv->addStream(senderID);
   }   
   else {
      sage::printLog("pixelDownloader::addStream : receiver obj is NULL");
      return -1;
   }
         
   return 0;
}

int pixelDownloader::clearTile(int tileIdx)
{
   montagePair &monPair = montageList[tileIdx];
   sageMontage *mon = monPair.getFrontMon();
   shared->displayObj->removeMontage(mon);
   monPair.deactivate();

   return 0;
}

int pixelDownloader::clearScreen()
{
   for (int i=0; i<tileNum; i++) {
      clearTile(i);
   }
   
   return 0;
}

int pixelDownloader::swapMontages()
{
   bool activeMontage = false;
   
   for (int i=0; i<tileNum; i++) {
      montagePair &monPair = montageList[i];

      if (monPair.isActive()) {
         if (monPair.getClearFlag()) {
            clearTile(i);
         }   
         else {   
            monPair.swapMontage();
            shared->displayObj->replaceMontage(monPair.getFrontMon());
         }
         activeMontage = true;
      }      
   }
   
   if (activeMontage) {
      shared->displayObj->setDirty();
   }
   
   return 0;
}

void pixelDownloader::processSync(int frame, int cmd)
{
   if (!syncOn)
      return;
      
   syncFrame = frame;
   //if (shared->nodeID == 0)
   //std::cout << "receive sync " << syncFrame << std::endl;

   if (updatedFrame == syncFrame) {
      swapMontages();
   }
   else {
      bool screenUpdate = false;
      for (int i=0; i<tileNum; i++) {
         montagePair &monPair = montageList[i];
         if (monPair.getClearFlag()) {
            clearTile(i);
            screenUpdate = true;
         }   
      }
      if (screenUpdate)
         shared->displayObj->setDirty();
            
      if (cmd == SKIP_FRAME) {
         updatedFrame = syncFrame;
         
         
      }
   }      
}

int pixelDownloader::enqueConfig(char *data)
{   
   char *configData = new char[strlen(data)+1];
   if (!configData) {
      sage::printLog("pixelDownloader::enqueConfig : unable to allocate memory");
      return -1;
   }
      
   strcpy(configData, data);
   configQueue.push_back(configData);
   
   return 0;
}

bool pixelDownloader::reconfigDisplay(int confID)
{
   if (dispConfigID >= confID) {
      sage::printLog("pixelDownloader::reconfigDisplay : configuration ID error");
      return false;  
   }   
   
   char *configStr = NULL;
   while (dispConfigID < confID) {
      if (configQueue.size() == 0)
         return false;

      configStr = configQueue.front();
      dispConfigID++;
      configQueue.pop_front();
   }

   int oldRcvs = activeRcvs;
   displayActive = false;
   
   sageRotation orientation;
   sscanf(configStr, "%d %d %d %d %d %d", &windowLayout.x, &windowLayout.y, 
      &windowLayout.width, &windowLayout.height, &activeRcvs, (int *)&orientation);
   windowLayout.setOrientation(orientation);
   
   // when the window locates on a neighbor tiled display
   if (windowLayout.width == 0 || windowLayout.height == 0) {
      if (syncOn) {
         for (int i=0; i<tileNum; i++)
            montageList[i].clear();
      }
      else {   
         clearScreen();
         shared->displayObj->setDirty();
      }   
      return true;
   }   
   
   partition->setDisplayLayout(windowLayout);
   partition->clearBlockTable();
   
   for (int i=0; i<tileNum; i++) {
      montagePair &monPair = montageList[i];
      sageMontage *mon = NULL;
      
      sageRect tileRect = shared->displayObj->getTileRect(i);
      if (!tileRect.crop(windowLayout)) {
         if (syncOn)
            monPair.clear();
         else {  
            clearTile(i);
            shared->displayObj->setDirty();
         }   
         continue;
      }   
      
      displayActive = true;

      partition->setTileLayout(tileRect);
      sageRect viewPort = partition->getViewPort();
      sageRect blockLayout = partition->getBlockLayout();

      viewPort.moveOrigin(blockLayout);
      
      if (monPair.isActive()) {
         //if (shared->nodeID == 5)
         //   std::cerr << "montage active" << std::endl;
         mon = monPair.getBackMon();
         *(sageRect *)mon = tileRect;
         mon->init(viewPort, blockLayout, orientation);
         monPair.renew();
      }
      else {   
         //if (shared->nodeID == 5)
         //   std::cerr << "montage inactive" << std::endl;
         mon = monPair.getFrontMon();
         //*(sageRect *)mon = tileRect;
         //mon->init(viewPort, blockLayout, orientation);
         int monIdx = shared->displayObj->addMontage(mon);
         
         mon = monPair.getBackMon();
         *(sageRect *)mon = tileRect;
         mon->init(viewPort, blockLayout, orientation);
         mon->monIdx = monIdx;
         monPair.renew();

         monPair.activate();
      }
      
      partition->genBlockTable(i);
   }   
   
   frameSize = blockSize * partition->tableEntryNum();
   
   if (oldRcvs != activeRcvs)
      updateType = SAGE_UPDATE_SETUP;
   
   return true;
}

int pixelDownloader::downloadPixelBlock(sagePixelBlock *block, montagePair &monPair)
{
   sageMontage *mon = monPair.getBackMon();
   
   mon->loadPixelBlock(block);
   //monPair.update();

   return 0;
}

int pixelDownloader::fetchSageBlocks()
{
   // fetch block data from the block buffer
   sageBlockGroup *sbg;
   
   while (sbg = blockBuf->front()) {
      //std::cout << "get block group" << std::endl;
      if (sbg->getFlag() == sageBlockGroup::END_FRAME) {
         //std::cout << "end frame " << curFrame << std::endl;
         updatedFrame = curFrame;
         frameCounter++;
         
         // calculate packet loss
         packetLoss += frameSize-(frameBlockNum*blockSize);
         frameBlockNum = 0;
         
         if (syncOn) {
            if (updatedFrame <= syncFrame) {
               swapMontages();
            }
            else {
               shared->syncClientObj->sendSlaveUpdate(updatedFrame, instID, activeRcvs, updateType);   
               updateType = SAGE_UPDATE_FOLLOW;
               //std::cout << "send update " << updatedFrame << std::endl;
               status = PDL_WAIT_SYNC;
            
               blockBuf->next();
               blockBuf->returnBG(sbg);
            
               return status;
            }   
         }
         else {
            swapMontages();
         }
      }
      else if (sbg->getFlag() == sageBlockGroup::CONFIG_UPDATE) {
         if (configID < sbg->getConfigID()) {
            if (reconfigDisplay(sbg->getConfigID()))
               configID = sbg->getConfigID();
            else {
               status = PDL_WAIT_CONFIG;
               return status;   
            }   
         }
      }
      else if (sbg->getFrameID() > updatedFrame) {
         if (configID < sbg->getConfigID()) {
            if (reconfigDisplay(sbg->getConfigID()))
               configID = sbg->getConfigID();
            else {
               status = PDL_WAIT_CONFIG;
               return status;
            }   
         }
         
         bandWidth += sbg->getDataSize() + GROUP_HEADER_SIZE;
         curFrame = sbg->getFrameID();
         frameBlockNum += sbg->getBlockNum();
         
         for (int i=0; i<sbg->getBlockNum(); i++) {
            sagePixelBlock *block = (*sbg)[i];
            
            if (!block)
               continue;
            
            //std::cout << "block header " << (char *)block->getBuffer() << std::endl;
             
            blockMontageMap *map = (blockMontageMap *)partition->getBlockMap(block->getID());
            int bx = block->x, by = block->y;

            while(map) {
               block->translate(map->x, map->y);
               //std::cout << "block montage " << map->infoID << " id " << block->getID() << " pos " << block->x << " , " << block->y << std::endl;
               downloadPixelBlock(block, montageList[map->infoID]);
               block->x = bx;
               block->y = by;
               map = (blockMontageMap *)map->next;
            }
            //std::cout << "download block " << block->getID() << std::endl;
         }
      }
      else 
         sage::printLog("pixelDownloader::fetchSageBlocks : invalid block order");
      
      blockBuf->next();
      blockBuf->returnBG(sbg);
   }
   
   status = PDL_WAIT_DATA;
   //std::cout << "exit fetch" << std::endl;
   return status;
}

int pixelDownloader::evalPerformance(char **frameStr, char **bandStr)
{
   //Calculate performance here
   double elapsedTime = perfTimer.getTimeUS();
   
   if (elapsedTime > 1000000.0*reportRate && reportRate > 0) {
      *bandStr = new char[TOKEN_LEN];

      float obsBandWidth = (float) (bandWidth * 8.0 / (elapsedTime));
      float obsLoss = (float) (packetLoss * 8.0 / (elapsedTime));
      bandWidth = 0;
      packetLoss = 0;
      sprintf(*bandStr, "%d %7.2f %7.2f %d", instID, obsBandWidth, obsLoss, frameSize);      

      if (displayActive) {
         *frameStr = new char[TOKEN_LEN];
         float frameRate = (float) (frameCounter.getValue()*1000000.0/elapsedTime);
         frameCounter.reset();
         sprintf(*frameStr, "%d %f", instID, frameRate);
      }
            
      perfTimer.reset();
   }
   
   return 0;
}

int pixelDownloader::setDepth(float depth)
{
   for (int i=0; i<tileNum; i++) {
      montageList[i].setDepth(depth);
   }
   
   return 0;
}

pixelDownloader::~pixelDownloader()
{
   for (int i=0; i<tileNum; i++) {
      montagePair &monPair = montageList[i];
      sageMontage* mon = monPair.getFrontMon();
      shared->displayObj->removeMontage(mon);
      monPair.deleteMontage();
   }
   
   shared->displayObj->setDirty();         

   delete [] montageList;
   delete recv;
   delete blockBuf;

   for (int i=0; i<configQueue.size(); i++) {
      char *configData = configQueue.front();
      configQueue.pop_front();
      delete [] configData;
   }
}
